Skip to content

catalogue: align published method lists with shipped bundles; backfill index fields - #458

Merged
Alexgodoroja merged 4 commits into
mainfrom
fix/catalogue-listing-drift
Sep 1, 2026
Merged

catalogue: align published method lists with shipped bundles; backfill index fields#458
Alexgodoroja merged 4 commits into
mainfrom
fix/catalogue-listing-drift

Conversation

@Alexgodoroja

@Alexgodoroja Alexgodoroja commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

✅ Signed — safe to merge

The catalogue is served straight off main via raw.githubusercontent.com, and pilotctl fail-closes on a bad signature: it rejects the entire catalogue, not one entry. So the listing changes here invalidated catalogue.json.sig and the branch could not be merged until it was re-signed. That is done.

catalogue/catalogue.json.sig is regenerated with the catalogue key. sign-catalogue refuses any key that does not match the public key embedded in internal/catalogtrust, so acceptance is itself proof the right key was used.

Verified three ways:

  • cmd/pilotctl TestLoadCatalogue_VerifiesSignedRepoCatalogue passes again, alongside FailsClosedWithoutSignature and FailsOnTamper — the tests that were red on this branch.
  • A released v1.13.9 pilotctl (not a build from this branch) loads the re-signed catalogue from disk and lists all 29 apps.
  • Before signing, the same client rejected it outright: catalogue signature: catalogtrust: catalogue signature verification failed, with no apps listed at all.

That last point is worth keeping: CI would have caught a merge with a stale signature regardless, because Go and Architecture gates both run the signature test.

One separable commit

go.mod: bump to 1.25.13 is unrelated to the catalogue. govulncheck resolves its toolchain from go-version-file: go.mod, so the 1.25.12 pin meant CI scanned against a stdlib carrying five disclosed vulnerabilities (GO-2026-6218, -6090, -6088, -5972, -5026) and failed every PR opened against the repo today, Go-touching or not. All five are stdlib-only and all five are fixed in go1.25.13. main still shows green only because it has not been scanned since 2026-08-09, before these were published. Happy to split it out if you would rather land it on its own.

Appendix: what the red CI looked like before signing

TestLoadCatalogue_VerifiesSignedRepoCatalogue fails with

loadCatalogue on signed repo catalogue: catalogue signature: catalogtrust: catalogue signature verification failed

That single failure is what reds Go (ubuntu), Go (macos) and Architecture gates — the last runs go test ./pkg/... ./cmd/... ./internal/..., which includes the same test. This is the repo refusing to accept a catalogue whose signature does not match, which is exactly the protection this PR depends on. It cannot be merged wrong.

The precedent is #447, the last catalogue change: it committed catalogue/catalogue.json.sig alongside catalogue.json and CI passed. Same is needed here — re-sign on this branch and the three checks go green.

govulncheck is unrelated to this change. It reports 5 Go standard library vulnerabilities (GO-2026-5026 and others, fixed in go1.25.13) reached through net/http. This PR touches no Go code; the check passed on #457 only because that run predates the disclosure, and main's own nightly has been failing for several nights. It needs a Go toolchain bump, separately from this PR.

What this fixes

I audited all 29 catalogue apps across three published surfaces — catalogue.json, apps/<id>/metadata.json, and the appstore-meta API — against ground truth: each bundle's signed manifest exposes list, cross-checked with the installed app's own <app>.help on a clean Linux node.

Twelve apps published a method list that did not match the app they ship.

Methods

  • io.pilot.orthogonal — removed three methods that do not exist. The v0.1.1 bundle exposes 7 methods; orthogonal.check, orthogonal.transactions and orthogonal.usage are not among them. orthogonal is metered, so these were precisely the calls an agent would make to check its own spend. The method that does exist is orthogonal.balance — which was itself undocumented, and is now listed.
  • Documented methods the bundles expose but the listing omitted: agentphone.setup, agentphone.status; bowmark.balance; kinetic.balance; rentahuman.balance; ideon-free.generate, ideon-free.poll; and <app>.help for didit, insforge, mysql, postgres, tldr.
    Summaries are each app's own runtime help text, not new marketing copy.
  • Deliberately left alone: wallet.hookPreSendMessage / .hookPostRecvMessage. They are daemon lifecycle hooks rather than user-callable methods, and no other app documents its hooks. (Webhook methods, which are user-facing, are already documented everywhere they exist.)

The balance cases matter on their own: three spend-bearing apps shipped a way to check remaining budget that an agent could not discover from any published surface.

Names — so the same app is called the same thing everywhere

app before after
io.pilot.mysql Mysql MySQL
io.pilot.tldr Tldr tldr
io.telepat.ideon-free Ideon-free (index: empty) Ideon (Free)

The API and website already said MySQL / tldr / Ideon (Free); the catalogue and metadata disagreed. Because ideon-free's index display_name was empty, pilotctl appstore catalogue printed its entire description paragraph in the column where every other app shows a short name.

Index fields

  • metadata_sha256 for io.pilot.agentphone was stale, not wrong. The pin said d1b5fea7… while both the repo file and raw.githubusercontent serve 452d988a…. Someone edited the listing without re-pinning, so pilotctl appstore view io.pilot.agentphone printed:
    warn: could not load detail metadata: metadata sha256 mismatch … the host served different bytes than the catalogue pinned
    
    and fell back to a reduced listing. Re-pinned — along with every metadata.json touched here.
  • categories empty → filled from each listing: agentphone, miren, orthogonal, otto, plainweb, postgres.
  • license empty for orthogonalMIT, matching its listing.
  • bundle_size was 0 for agentphone, ideon-free, miren, otto, plainweb, postgres, and wrong for sixtyfour (declared 4,898,153 B, serves 5,058,693 B). All set from the bytes actually served at each bundle_url. Bundle checksums were all correct — only the size field was stale.
  • changelog head trailed the released version for agentphone (v0.3.0 vs shipped v0.3.1) and orthogonal (v0.1.0 vs v0.1.1).

Not in this PR

Four apps cannot run on Linux at all — cosift, wallet and ideon-free have no per-platform bundles map, so pilotctl falls back to their single bundle_url, which is a darwin build that installs cleanly and then never spawns; slipstream has a bundles map but no linux/arm64 entry, so it correctly hard-errors. Fixing those needs Linux bundles built and published first, so they are deliberately left out rather than half-fixed here. Worth noting the mechanism is sound and already used by 25 of 29 apps — these four are legacy entries that never got one.

Verification

  • all 28 metadata.json parse
  • every metadata_sha256 matches its file on disk
  • zero remaining differences between documented methods and shipped exposes (was 12)
  • catalogue.json stays "version": 2loadCatalogue fail-closes on anything else
  • diff is 125 insertions / 38 deletions with no reformatting churn; io.telepat.ideon-free/metadata.json is emitted by Go (whose << escaping a Python round-trip cannot reproduce) so it was edited textually to keep the diff to the intended lines

🤖 Generated with Claude Code

…l index fields

Audited all 29 catalogue apps against three published surfaces (catalogue.json,
apps/<id>/metadata.json, the appstore-meta API) and against ground truth: each
bundle's signed manifest `exposes` list, cross-checked with the installed app's
own `<app>.help` on a clean Linux node.

Twelve apps published a method list that did not match the app they ship.

Methods
- io.pilot.orthogonal: drop orthogonal.check / .transactions / .usage. The
  v0.1.1 bundle exposes 7 methods and none of these three. orthogonal is
  metered, so these were exactly the calls an agent would make to check its own
  spend — the method that does exist is orthogonal.balance.
- Document methods the bundles expose but the listing omitted:
  agentphone.setup, agentphone.status; bowmark.balance; kinetic.balance;
  rentahuman.balance; ideon-free.generate, ideon-free.poll; and <app>.help for
  didit, insforge, mysql, postgres, tldr.
  Summaries are each app's own runtime help text, not new copy.
  wallet.hookPreSendMessage / .hookPostRecvMessage are deliberately left
  undocumented — they are daemon lifecycle hooks, not user-callable methods,
  and no other app documents its hooks.

Names, so the same app is called the same thing everywhere
- mysql:      "Mysql"      -> "MySQL"
- tldr:       "Tldr"       -> "tldr"
- ideon-free: "Ideon-free" -> "Ideon (Free)", and the index display_name was
  empty, so `pilotctl appstore catalogue` printed its whole description
  paragraph in the name column.

Index fields
- metadata_sha256 for io.pilot.agentphone was stale, not wrong: the pin said
  d1b5fea7 while both the repo file and raw.githubusercontent served 452d988a.
  `pilotctl appstore view io.pilot.agentphone` therefore warned and fell back
  to the teaser. Re-pinned, along with every metadata.json touched here.
- categories were empty for agentphone, miren, orthogonal, otto, plainweb and
  postgres; filled from each listing.
- license was empty for orthogonal; set to MIT to match its listing.
- bundle_size was 0 for agentphone, ideon-free, miren, otto, plainweb and
  postgres, and wrong for sixtyfour (4898153 declared, 5058693 served). All set
  from the bytes actually served at each bundle_url.
- changelog head trailed the released version for agentphone (v0.3.0 vs v0.3.1)
  and orthogonal (v0.1.0 vs v0.1.1).

catalogue.json stays "version": 2 — `loadCatalogue` fail-closes on any other
value. io.telepat.ideon-free/metadata.json is emitted by Go, whose <
escaping a Python round-trip cannot reproduce, so it was edited textually to
keep the diff to the intended lines.

Verified: all 28 metadata.json parse; every metadata_sha256 matches its file;
zero remaining differences between documented methods and shipped `exposes`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Alexgodoroja and others added 3 commits August 27, 2026 20:36
… listing

Four more index entries had both fields empty on every surface, so the earlier
catalogue-vs-listing drift check could not see them — nothing disagreed because
nothing was set anywhere.

Values come from the `listing` block of submissions/<id>/submission.json in
pilot-protocol/app-template, which is what the publish pipeline reads:

  io.pilot.insforge  Apache-2.0   developer-tools, data, storage
  io.pilot.mysql     GPL-2.0      database, data, sql
  io.pilot.didit     Proprietary  identity, verification, compliance, security, kyc
  io.pilot.tldr      MIT          developer-tools, documentation, cli

insforge's submission spells its categories "Developer Tools" / "Data & Storage";
normalised here to the catalogue's existing all-lowercase vocabulary, which
already contains developer-tools, data and storage.

io.telepat.ideon-free is left as-is: it is third-party, has no submissions/
directory in app-template, and its licence is not ours to assert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The signature is over the exact bytes of catalogue.json, so the listing and
index changes on this branch invalidated it. pilotctl fail-closes on a bad
signature — it rejects the whole catalogue rather than one entry — so the
detached signature has to travel with the data, the same way #447 did it.

Signed with the catalogue key; sign-catalogue refuses any key that does not
match the public key embedded in internal/catalogtrust, so acceptance is itself
proof the right key was used.

Verified: cmd/pilotctl TestLoadCatalogue_VerifiesSignedRepoCatalogue passes
again, along with FailsClosedWithoutSignature and FailsOnTamper; and a released
v1.13.9 pilotctl loads the re-signed catalogue from disk and lists all 29 apps.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
govulncheck resolves the toolchain from go-version-file: go.mod, so the 1.25.12
pin meant CI scanned against a stdlib with five disclosed vulnerabilities and
failed every PR opened against the repo today, whether or not it touched Go:

  GO-2026-6218  net/url        GO-2026-6090  crypto/tls
  GO-2026-6088  encoding/xml   GO-2026-5972  encoding/asn1
  GO-2026-5026  net/http

All five are standard-library only and all five are fixed in go1.25.13, so the
patch bump clears the whole set. main has not been scanned since 2026-08-09,
before these were published, which is why it still shows green.

This is separable from the catalogue work in the rest of the branch and can be
cherry-picked out or landed on its own; it is here because the branch cannot go
green without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Alexgodoroja
Alexgodoroja merged commit c2f3b40 into main Sep 1, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant